home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1997 January: Mac OS SDK / Dev.CD Jan 97 SDK2.toast / Development Kits (Disc 2) / OpenDoc / Developer Documentation / Recipes, Tech Notes & Articles / Recipes / Data Interchange / Linking Recipes Part 2 < prev    next >
Encoding:
Text File  |  1996-04-21  |  30.6 KB  |  511 lines  |  [TEXT/ttxt]

  1. OpenDoc™ Recipes
  2.  
  3.  
  4. Linking Recipes Part 2
  5. By The OpenDoc Design Team
  6. April, 1995
  7.  
  8.  
  9. © 1993-1996  Apple Computer, Inc. All Rights Reserved.
  10. Apple, the Apple logo, AppleScript, Bento, Macintosh, QuickTime, and OpenDoc are 
  11. registered trademarks of Apple Computer, Inc.
  12. Finder, Mac, and QuickDraw are trademarks of Apple Computer, Inc. 
  13. SOM, SOMObjects, and System Object Model are licensed trademarks of IBM Corporation.
  14.  
  15.  
  16. Continued from Part 1...
  17.  
  18.  
  19. Posting a Link Specification to the Clipboard or Drag and Drop Object
  20.  
  21. When data is copied to the clipboard or drag and drop object, the part should usually write a link specification in addition to content.  The link specification indicates to any part performing a paste or drop that a persistent link may be created to the original content, using any of the representations present in the kODPropContents property.
  22.  
  23. The data in a link spec is private to the part writing the spec.  The data written to the spec will be returned to the part via its CreateLink method if a link is actually created.  The part needs to be able to identify the selected content from the link spec data.  Because the link spec is only valid during the lifetime of the part, the data can contain pointers to information maintained by the part.
  24.  
  25. This example shows adding a link specification to the clipboard.  To add a link spec to a drag and drop object, just replace clipContentSU with the content storage unit of the drag and drop object.
  26.  
  27. ODClipboard* clipboard = mySession->GetClipboard(ev);
  28. ODStorageUnit* clipContentSU = clipboard->GetContentStorageUnit(ev);
  29.  
  30. ODLinkSpec* linkSpec = kODNULL;
  31. ODVolatile(linkSpec);
  32.  
  33. TRY
  34.  
  35.   clipContentSU->AddProperty(ev, kODPropLinkSpec);
  36.  
  37.   linkSpec = myDraft->CreateLinkSpec(ev, somThis->fPartWrapper, link spec byte array);
  38.  
  39.   linkSpec->WriteLinkSpec(ev, clipContentSU);
  40.  
  41. CATCH_ALL
  42.  
  43.   ODReleaseObject(linkSpec);
  44.   RERAISE;
  45.  
  46. ENDTRY
  47.  
  48. ODReleaseObject(linkSpec);
  49.  
  50. When Not to Post a Link Specification
  51.  
  52. When copying data to a content storage unit, parts should not write a link specification in any of the following cases:
  53.  
  54. • The content storage unit belongs to a link source object, not the clipboard or drag and drop object.
  55.  
  56. • The part's draft does not have write permission.  Before writing a link spec, check the draft permissions.  If the permissions don't allow writing, a part should not write a link spec.  Since the draft cannot be saved, the internal links cannot be preserved, and an external link created to another document will be abandoned when the draft is closed.
  57.  
  58. • The data is in a link destination maintained by the part.
  59.  
  60. • The part itself is embedded in a link destination (the link status of its display frame is kODInLinkDestination).
  61.  
  62. • The data contains only a portion of an existing link destination.  The difficulty is maintaining the source link when the destination link is updated.  It is in general impossible to determine what content the source link should assume when the destination changes.
  63.  
  64. • The data includes an entire link destination exactly.  In this case, copying the content and pasting will create a another link destination if pasted into the same document (unless the user picks a lower-fidelity kind that doesn't support linking).  If the part wrote a link specification and the new destination chose to create a link, an second link would be created between the existing and new destinations, probably not what the user wanted.  The user should have to select the source to create a destination in another document.
  65.  
  66. Removing a Link Spec from the Clipboard
  67.  
  68. It is the responsibility of the part writing a link spec to remove it from the clipboard when:
  69.  
  70. •  It becomes infeasible to create the link, for example, because the potential source content is deleted.
  71.  
  72. • The part's ReleaseAll() method is called.
  73.  
  74. In order to determine whether a link spec written by a part still resides on the clipboard,  the part must remember the update ID of the clipboard when it writes the link specification.  The following code fragment can be used to remove a link spec:
  75.  
  76. ODArbitrator* arbitrator = mySession->GetArbitrator(ev);
  77. ODFrame* clipboardOwner = arbitrator->AcquireFocusOwner(ev, clipboardFocus);
  78. if ( (activeFrame == clipboardOwner) ||
  79.  (arbitrator->RequestFocus(ev, clipboardFocus, activeFrame )) )
  80. {
  81.   ODClipboard* clipboard = mySession->GetClipboard(ev);
  82.  
  83.   TRY
  84.     if ( myClipboardUpdateID == clipboard->GetUpdateID(ev) )
  85.     {
  86.       ODStorageUnit* clipContentSU = clipboard->GetContentStorageUnit(ev);
  87.  
  88.       // Focus to the link spec property
  89.       clipContentSU->Focus(ev, 
  90.           kODPropLinkSpec, 
  91.           kODPosUndefined, 
  92.           kODNULL, 
  93.           0, 
  94.           kODPosUndefined);
  95.  
  96.       // Remove the property
  97.       clipContentSU->Remove(ev);
  98.     }
  99.  
  100.   CATCH_ALL
  101.  
  102.     // Ignore errors.
  103.  
  104.   ENDTRY
  105. }
  106. ODReleaseObject(ev, clipboardOwner);
  107.  
  108. Specifying Kinds Supported via 'nmap' Resources
  109.  
  110. The settings and choices in the Paste As dialog rely on accurate information about the kinds supported by a part.  See the document "Binding and 'nmap' Recipe" for details on how to specify the kinds supported by a part.
  111.  
  112. Implementing Paste with Link (Incorporating Content)
  113.  
  114. When the user chooses the "Paste As…" menu item, the part should call the ShowPasteAsDialog method of the clipboard or drag and drop opbject to display the dialog.  If the ODPasteAsResult value set by that method specifies a link should be created to the source, this recipe should be followed.
  115.  
  116. Parts should enable the "Paste As…" menu item only if the draft's permissions allow writing.
  117.  
  118. Basically, the receiving part should ignore the content on the clipboard, and read the link spec value there using ODLinkSpec::ReadLinkSpec.  The link object is obtained via the receiving part's draft's AcquireLink method.  The receiving part should maintain an ODLinkInfo structure for the destination of the link, initialized from values of the link object and from the ODPasteAsResult value as set by the user.
  119.  
  120. Because pasting a link does not get content from the clipboard, your part should not call the clipboard's ActionDone method.
  121.  
  122. The routine MyPasteWithLink shown here takes three parameters.  The first is the content storage unit of the clipboard or drag and drop object.  The second is the structure returned by the ShowPasteAsDialog method of either the clipboard or drag and drop object.  The third is a boolean indicating whether content is pasted from the clipboard or the drag and drop object; this makes a difference in how the routine adds undo actions.
  123.  
  124. MyPasteWithLink shows how the part performing the paste is expected to add undo history.  Because link creation involves both the source and destination parts, an undo transaction is used to capture actions by both parts.  If the link is being created from the clipboard, the destination must begin an undo transaction before calling AcquireLink.  If the link is created during a drop, the part that initiated the drop is assumed to have already started an undo action, so this part simply adds its undo data as a single action.
  125.  
  126. Note that MyPasteWithLink will register for update notification even if the user has requested manual updates.  Because cross-document links may be created asynchronously, its not always possible to read data from a link immediately.  Registering for notification gives the source part a chance to deliver the data.  When this part's LinkUpdated method is called, and content is available (i.e. GetContentStorageUnit doesn't return the error kODErrNoLinkContent), the part should unregister if the destination updates manually, and there are no automatic destinations of the same link maintained by this part.
  127.  
  128. For simplicity, translation of content incorporated through a link is not covered here.  Examples of translation appear in the section “Embedding a Part via the Paste As Dialog” in the Data Interchange Basics document.
  129.  
  130. void MyPasteWithLink(Environment *ev,
  131.   ODStorageUnit* contentSU,
  132.   ODPasteAsResult pasteAsResult,
  133.   ODBoolean fromClipboard)
  134. {
  135.   ODDraft* myDraft = fSOMSelf->GetStorageUnit(ev)->GetDraft(ev);
  136.   ODLinkSpec* linkSpec = kODNULL;
  137.  
  138.   ODIText* undoActionName = kODNULL;
  139.   ODIText* redoActionName = kODNULL;
  140.   ODActionData noActionData = (ODActionData) CreateByteArrayStruct(kODNULL,0);
  141.   
  142.   ODUndo* undo = fSOMSelf->GetStorageUnit(ev)->GetSession(ev)->GetUndo(ev);
  143.   
  144.   // For demonstration, locals are used; your part must integrate these into its content
  145.   ODLinkInfo linkInfo;
  146.   ODLink* link = kODNULL;
  147.  
  148.   SOM_TRY
  149.   
  150.     // Initialize text for undo
  151.     undoActionName = CreateIText(smRoman, langEnglish, "Undo Paste");    // Not localizable!
  152.     redoActionName = CreateIText(smRoman, langEnglish, "Redo Paste");
  153.  
  154.     // Read the link spec from the content storage unit.  The link spec is 
  155.     //   created with null ODPart and ODByteArray pointers since it is initialized by
  156.     //   ReadLinkSpec.
  157.  
  158.     linkSpec = myDraft->CreateLinkSpec(ev, kODNULL, kODNULL);
  159.  
  160.     // Focus to the link spec property; ReadLinkSpec will focus to the appropriate value.
  161.     contentSU->Focus(ev,
  162.          kODPropLinkSpec,  
  163.          kODPosUndefined,   
  164.          kODNULL,   
  165.          0,   
  166.          kODPosUndefined);
  167.  
  168.     linkSpec->ReadLinkSpec(ev, contentSU);
  169.  
  170.     // Establish the link
  171.     TRY
  172.  
  173.       // If this is a paste from the clipboard, start an undo transaction
  174.       if ( fromClipboard )
  175.       {
  176.         undo->AddActionToHistory(ev,
  177.                                  somThis->fPartWrapper,
  178.                                  &noActionData,
  179.                                  kODBeginAction,
  180.                                  undoActionName,
  181.                                  redoActionName);
  182.       }
  183.  
  184.       link = myDraft->AcquireLink(ev, kODNULLID, linkSpec);
  185.  
  186.       linkInfo.change = kODUnknownUpdate; // not yet updated
  187.       GetDateTime(&linkInfo.creationTime);
  188.       linkInfo.kind = ODISOStrFromCStr(pasteAsResult.selectedKind);
  189.       linkInfo.autoUpdate = pasteAsResult.autoUpdateSetting;
  190.  
  191.       // Add link and linkInfo to the content model of this part.
  192.       //   This is part specific.
  193.       …
  194.  
  195.       // Add this action to the undo stack.
  196.       //   If this is a paste from the clipboard, end the undo transaction started above.
  197.       //   Otherwise, this must be a drop, so add to the existing transaction.
  198.       ODActionData* actionData = …
  199.       undo->AddActionToHistory(ev,
  200.                                somThis->fPartWrapper,
  201.                                actionData,
  202.                                (fromClipboard ? kODEndAction : kODSingleAction),
  203.                                undoActionName,
  204.                                redoActionName);
  205.  
  206.     CATCH_ALL
  207.  
  208.       // Abort the undo transaction.
  209.       if ( fromClipboard )
  210.       {
  211.         undo->AbortCurrentTransaction(ev);
  212.       }
  213.       RERAISE;
  214.  
  215.     ENDTRY
  216.  
  217.     // Always register for notification that the initial link content is available.
  218.     //   If the user specified manual updating, this part should unregister after
  219.     //   LinkUpdated is called.
  220.     if ( !fSOMSelf->IsRegistered(ev, link) )
  221.     {
  222.       // RegisterDependent will call LinkUpdated.
  223.       link->RegisterDependent(ev, somThis->fPartWrapper, linkInfo.change);
  224.     }
  225.     else
  226.     {
  227.       // Get update now
  228.       fSOMSelf->LinkUpdated(ev, link, link->GetUpdateID(ev));
  229.     }
  230.  
  231.   SOM_CATCH_ALL
  232.   
  233.   SOM_ENDTRY
  234.   
  235.   DisposeIText(undoActionName);
  236.   DisposeIText(redoActionName);
  237.   delete linkSpec;
  238. }
  239.  
  240. Implementing Paste with Link (Embedding Content)
  241.  
  242. Any part that supports embedding and linking should allow the user to embed content from the clipboard and create a link.  When content from a link is embedded, the destination part (the part performing the paste) does not clone the content storage unit of the clipboard or drag and drop object.  Instead, the part reads the link spec from the content storage unit, acquires the link from the link spec, and creates an embedded part by cloning the content storage unit of the link.  When the link is updated, the destination part discards the embedded part and creates a new embedded part from the new link content.  The embedded part is not involved in the maintenance of the link.  The link border appears around the embedded part's frame and is drawn by the destination part.
  243.  
  244. Because pasting a link does not get content from the clipboard, your part should not call the clipboard's ActionDone method.
  245.  
  246. When content is embedded, the user may choose a particular content kind or specify a translation to another kind.  The Data Interchange Basics recipe "Embedding a Part via the Paste As Dialog" describes the steps the part performing the paste takes to embed the desired content kind.  The recipe is directly applicable to embedding linked content; in this case, the content storage unit of the link is cloned (as above), and then any necessary translation is performed. 
  247.  
  248. Drop Result when Pasting a Link
  249.  
  250. When a link is created using drag and drop, be sure your part's Drop method returns the ODDropResult value kODDropCopy.
  251.  
  252. Registering for Update Notification
  253.  
  254. The “Implementing Paste with Link” recipes demonstrate how a destination part can register with the link to receive automatic notifications when the link is updated with new content.  When a new link destination is created, parts should always register to receive notification that the initial content is available.
  255.  
  256. Parts register for updates by calling the RegisterDependent method of the link method.  The part supplies the update ID of the content it last read from the link.  To get the initial content from the link, a part can use the update ID constant kODUnknownUpdate to ensure it receives notification.  Parts receive notification of an update via their LinkUpdated method.
  257.  
  258. If a part's LinkUpdated method is called after registering to receive the initial content from a link, the part should unregister if the user specified that the destination should update manually.
  259.  
  260. Parts should register for notification when a part is internalized if the link updates automatically and the link is visible or may affect layout of the part.  When your part calls RegisterDependent, its LinkUpdated method may be called before RegisterDependent returns, so be sure to call RegisterDependent only when your part is prepared to receive a notification.  Parts may unregister and re-register for notification as they wish.  However, parts must be careful to register only once with the same link, even if they maintain two or more destinations of the same link.
  261.  
  262. If a part is in a read-only draft, you may call RegisterDependent, but your part won't receive notification (your part's LinkUpdated method won't be called).
  263.  
  264. Undoable Actions and Linking
  265.  
  266. The following user actions involving links should be undoable (this list may not be exhaustive):
  267.  
  268. • Pasting content creating a link to the source.
  269. • Pasting content containing existing links.
  270. • Breaking a link at the source or destination via the Link Info dialog.
  271. • Deleting a selected link and its content, or deleting content containing one or more source or destination links.
  272. • Cutting content containing one or more source or destination links.
  273.  
  274. The updating of links, at the source or destination, is not an undoable user operation.  A part updating the destination of a link does not have to create an undo action to restore its previous content.  Similarly, a part updating a link from source content does not need to create an action state to restore the link to its previous contents. 
  275.  
  276. When content containing a source link is removed by deleting or cutting, or when a source link is broken, your part should:
  277.   • save its reference to the link source object in undo action data and disassociate the link source from the content in whatever way is appropriate for the data format,
  278.   • call linkSource->SetSourcePart(ev, kODNULL); so the object no longer references the part.
  279. Note that your part should generally not update a removed or broken link source with empty content; just leave the link source in its existing state.
  280.  
  281. If the action is undone, the part should:
  282.   • reinstate the link source object into your parts content,
  283.   • call linkSource->SetSourcePart(ev, somThis->GetStorageUnit(ev)); to reestablish this part as the source of the link.
  284.  
  285. If a link or link source is broken, your part must change the link status of any affected frames; see the section “Frame Link Status” later in this document.
  286.  
  287. Similarly, links may be broken at their destination, or they may be deleted or cut along with the content at the destination of the link.  When this happens, your part should:
  288.   • save a reference to the link object in undo action data, and, if the link was explicitly broken, disassociate the link from the content.
  289.   • If this part is registered to receive update notifications, call link->UnregisterDependent(ev, somSelf->fPartWrapper) if this was the last automatically updated destination of the link in this part.
  290.  
  291. Your part should hold a reference to each link or link source objects in undo action data until the part's DisposeActionState method is called.
  292.  
  293. Undoing Operations involving Link Destinations
  294.  
  295. When designing and implementing undo and redo of operations that add or remove content, such as cut or paste, you must take into account the behavior of destination links in that content.  Your part does not treat updating  a link destination as an undoable action.  When a link updates, your part removes the existing content at the destination, and replaces it with new content from the link.  Your part does not create an undo action, and does not save the content that is replaced.  After an operation is performed, but before that operation is undone (and possibly redone), destination links in the affected content may update one or more times.  
  296.  
  297. As one example, consider the case where content containing a link destination is pasted.  If the link destination is updated before the paste is undone, the content associated with the paste may contain different objects.  If the user undoes the paste, your part must not assume the same objects that were created at the time of the paste are present.  Your part must undo the paste with whatever objects are currently in place.  In particular, the undo information your part associates with the paste should not include specific objects cloned from links.
  298.  
  299. Paste is not the only operation which can be affected by link updates before being undone or redone.  In general, care must be taken to ensure that undo action data respects updates to link destinations.  One approach is to maintain a content object representing each link destination, which acts as a proxy for the destination's actual content.  This eliminates direct references to intrinsic or embedded content objects within the link destination from undo action data.
  300.  
  301. Accessing a Link
  302.  
  303. Because source and destination parts may attempt to access a link simultaneously, parts must acquire a lock in order to get the storage unit containing the content of the link.  Many methods of classes ODLink and ODLinkSource require a valid link key that can only be obtained by successfully acquiring a lock first.  Parts must not access the storage unit returned by GetContentStorageUnit after unlocking the link.
  304.  
  305. In the current implementation of OpenDoc for MacOS, Lock returns FALSE immediately if the wait argument is non-zero and the lock cannot be granted.  In general, there is no urgency about updating links at either source or destination.  (In fact, the HI Guidelines recommend that parts do NOT update source links for each atomic change in source content when the changes occur repeatedly without pause, such as when keystrokes are being entered.)  The examples show passing a wait argument of zero to Lock().  If your part fails to obtain a lock, it can complete the link update later, for example by registering for idle time and attempting to obtain the lock during a future call to HandleEvent.
  306.  
  307. Basic Recipe for Writing to a Link
  308.  
  309. This basic recipe can be used to write the initial content into a link when created, and to update the link when the source content changes.  This example routine illustrates the use of promises, which must be fulfilled by this part's FulfillPromise method.  It uses the routine MyWriteToContentSU described in the Data Interchange Basics document.
  310.  
  311. To ensure that promises written into the link are fulfilled correctly, this routine adds a kODPropCloneKindUsed property before calling MyWriteToContentSU.  In their FulfillPromise methods, all parts should check for this property to see if a clone kind other than kODCloneCopy should be used.  When FulfillPromise is called to write to a link, BeginClone will fail with an inconsistent clone kind error if the clone kind is other than kODCloneToLink.
  312.  
  313. When the link is initially created or when its being updated, the justRewriting parameter should be kODFalse.  If the part's CreateLink method is called to create another destination of an existing link,  and the content at the source of the link hasn't changed since the link was last updated, the justRewriting parameter should be kODTrue.  See the recipe for creating a link for more information on when to pass kODTrue to the justRewriting parameter.
  314.  
  315. The promiseData parameter is whatever information the part needs to identify the promised content when its FulfillPromise method is called.
  316.  
  317. The contentShape parameter will be written into the link in the suggested frame shape annotation.  This property will be used as the frame shape should content be embedded at the destination of the link.
  318.  
  319. The partName parameter is passed thru to MyWriteToContentSU.
  320.  
  321. The Clear method removes the kODPropContents property from the link's content storage unit.  After calling Clear, your part must call GetContentStorageUnit, add the contents property, write promise values (or actual values) into the link, and call ContentUpdated to notify destinations.  After your part calls Unlock, OpenDoc will fulfill any promises for values that are used by destinations. 
  322.  
  323. Note that the Clear and ContentUpdated methods of ODLinkSource will return an exception if passed kODUnknownUpdate as the argument to the update parameter.
  324.  
  325. void MyWriteToLink (Environment *ev,
  326.   ODLinkSource* linkSource,
  327.   ODUpdateID updateID,
  328.   ODBoolean justRewriting,
  329.   ODByteArray* promiseData,
  330.   ODShape* contentShape,
  331.   ODIText* partName)
  332. {
  333.   ODVolatile(linkSource);
  334.  
  335.   ODLinkKey linkKey;
  336.   ODVolatile(linkKey);
  337.   
  338.   if ( linkSource->Lock(ev, 0, &linkKey) )
  339.   {
  340.     // Remember the previous updateID in case updating fails
  341.     ODUpdateID previousID = linkSource->GetUpdateID(ev);
  342.  
  343.     TRY
  344.       // Call Clear to remove the contents property and allow writing promises.
  345.       //   If justRewriting is true, use the existing updateID of the link
  346.       linkSource->Clear(ev, (justRewriting ? previousID : updateID), linkKey);
  347.  
  348.       ODStorageUnit* linkContentSU = linkSource->GetContentStorageUnit(ev, linkKey);
  349.  
  350.       // Add the clone kind used property so promises are fulfilled using kODCloneToLink
  351.       ODSetULongProp(ev, linkContentSU, kODPropCloneKindUsed, kODCloneKind, kODCloneToLink);
  352.  
  353.       fSOMSelf->MyWriteToContentSU(ev, linkContentSU, kODCloneToLink, promiseData, contentShape, partName);
  354.  
  355.       // If the link is being updated, and not just rewritten, inform the link source
  356.       //   that it has changed, so destinations can be notified.
  357.       if ( !justRewriting )
  358.         linkSource->ContentUpdated(ev, updateID, linkKey);
  359.  
  360.     CATCH_ALL
  361.  
  362.       // Clear a partially-updated link.  Destinations must deal
  363.       //   gracefully with a link content storage unit that has no
  364.       //   contents property.
  365.       linkSource->Clear(ev, previousID, linkKey);
  366.       linkSource->Unlock(ev, linkKey);
  367.       RERAISE;
  368.  
  369.     ENDTRY
  370.  
  371.     linkSource->Unlock(ev, linkKey);
  372.   }
  373. }
  374.  
  375. Implementing CreateLink
  376.  
  377. The creation of a link is initiated when the part performing a paste or drop calls its draft's GetLink method, passing in a link spec read from the clipboard or drag-and-drop container.  A recipe for pasting a link appears elsewhere in this document.
  378.  
  379. A link is created by a part's CreateLink method.  Any content kind written to the clipboard or drag-and-drop container should be available through the link.  When providing the initial content for a link, a part may write either promises or actual data.  By writing promises, a part can provide content for only the data kind(s) actually used by destinations of the link.  When a destination reads data through the link, the source part will be called to fulfill its promise for a particular content kind.  A separate document discusses promises in more detail.
  380.  
  381. This example implementation of CreateLink demonstrates how your part should add to the undo history when a new link source is created.  The action will be added to an undo transaction (started by the part performing the paste, or this part if the operation is a drop).  See the next section, "Better Undo Recipe for CreateLink", for the preferred way to handle undoing the link creation, although it is a little more work for your part.
  382.  
  383. CreateLink should only be called by drafts, not directly by parts.  This example demonstrates how your part may implement its CreateLink method.  It uses the routine MyWriteToLink to create the initial content of the link, using promises, and to ensure that all values are present each time an additional destination is created.  If your part doesn't write promises to a link, the else-clause in this example can be eliminated.
  384.  
  385. SOM_Scope ODLinkSource* SOMLINK MyPartCreateLink(MyPart *somSelf, Environment *ev,
  386.     ODByteArray* data)
  387. {
  388.   MyPartData *somThis = MyPartGetData(somSelf);
  389.  
  390.   ODLinkSource* linkSource = kODNULL; ODVolatile(linkSource);
  391.  
  392.   // Placeholders to be replaced by part-specific data
  393.   ODByteArray* promiseData;
  394.   ODShape* contentShape;
  395.   ODIText* partName;    // Optional
  396.   ODUpdateID sourceUpdateID;
  397.  
  398.   SOM_TRY
  399.  
  400.     // Call a part-specific method to get the link source if it already exists
  401.     linkSource = somSelf->MyGetExistingLinkSource(ev, data);
  402.  
  403.     if ( linkSource == kODNULL )
  404.     {
  405.       ODSession* session = somSelf->GetStorageUnit(ev)->GetSession(ev);
  406.  
  407.       // Call this part's draft to create the link source object
  408.       ODDraft* myDraft = somSelf->GetStorageUnit(ev)->GetDraft(ev);
  409.       linkSource = myDraft->CreateLinkSource(ev, somThis->fPartWrapper);
  410.  
  411.       // Add the link to the content model of this part.
  412.       //   This is part specific.
  413.       …
  414.  
  415.       // Associate a new update ID with the source content.
  416.       sourceUpdateID = session->UniqueUpdateID(ev);
  417.  
  418.       TRY
  419.  
  420.         MyWriteToLink (ev,
  421.             linkSource,
  422.             sourceUpdateID,
  423.             kODFalse,
  424.             promiseData,
  425.             contentShape,
  426.             partName);
  427.  
  428.       CATCH_ALL
  429.  
  430.         // Remove the link from the content model of this part
  431.         //…
  432.  
  433.         linkSource->Release(ev);
  434.         linkSource = kODNULL;
  435.         RERAISE;
  436.  
  437.       ENDTRY;
  438.  
  439.       // A new link source was sucessfully created, so add an action to the
  440.       //   undo stack.  The action names should never appear as menu items if
  441.       //   the parts implement undo properly.  The action data is part specific.
  442.       ODIText* undoActionName = CreateIText(smRoman, langEnglish, "Undo Create Link");    // Not localizable!
  443.       ODIText* redoActionName = CreateIText(smRoman, langEnglish, "Redo Create Link");
  444.       ODActionData* actionData = …
  445.       session->GetUndo(ev)->AddActionToHistory(ev,
  446.                                                somThis->fPartWrapper,
  447.                                                actionData,
  448.                                                kODSingleAction,
  449.                                                undoActionName,
  450.                                                redoActionName);
  451.       DisposeIText(undoActionName);
  452.       DisposeIText(redoActionName);
  453.     }
  454.     else
  455.     {
  456.       // If your part initialized the link with promises, add promises
  457.       //   for value kinds not already present in the link because
  458.       //   current destinations did not use them.
  459.  
  460.       // If the source content hasn't changed since the link was last updated,
  461.       //   rewrite the link, otherwise, update it now.  The variable sourceUpdateID
  462.       //   contains the update id currently associated with the content at the
  463.       //   source of the link.
  464.       ODBoolean justRewriting = (sourceUpdateID == linkSource->GetUpdateID(ev));
  465.  
  466.       TRY
  467.  
  468.         MyWriteToLink (ev,
  469.             linkSource,
  470.             sourceUpdateID,
  471.             justRewriting,
  472.             promiseData,
  473.             contentShape,
  474.             partName);
  475.  
  476.       CATCH_ALL
  477.  
  478.         // If the link couldn't be updated, CreateLink should fail rather than
  479.         //   return a linkSource without a contents property.
  480.         linkSource = kODNULL;
  481.         RERAISE;
  482.  
  483.       ENDTRY;
  484.     }
  485.  
  486.     // The result of this method must be released by the caller.
  487.     linkSource->Acquire(ev);
  488.  
  489.   SOM_CATCH_ALL
  490.   
  491.   SOM_ENDTRY
  492.  
  493.   return linkSource;
  494. }
  495.  
  496. Better Undo Recipe for CreateLink
  497.  
  498. If your part implements undo support as described in the recipe above, and the link is created via cross-document drag and drop, the undo action stack will contain two separate undo items; one for the drop, and another for the creation of the link source.  There are two undo items in this case because the call to the source part's CreateLink method is postponed until after the drag completes and returns to the source part.
  499.  
  500. Your part can avoid introducing a second undo item in this case by doing the following.  When you write a link spec to a drag and drop object's content storage unit, include in the part data information identifying the undo history transaction your part created for the drag and drop operation.  Then, in your CreateLink method, check for this information, and instead of adding an undo action as described above, modify some private data your part associates with the drag and drop undo transaction so that the link creation is undone (or redone) as well.
  501.  
  502. When CreateLink is called to get an existing link
  503.  
  504. CreateLink may be called multiple times with the same link spec to create multiple destinations of the same link.  If your part writes actual content into the link, it can just return the link object.  However, if your part writes promises, it must ensure all values are available in the link.
  505.  
  506. If CreateLink is called to get an existing link, its important that the link contain all value types available via the clipboard or drag and drop object.  If a part writes promises into a link, the link will contain only actual content used by current destinations; if the draft is saved, unfulfilled promises will be removed.  When CreateLink is called, the part must ensure that at least a promise is present for all value types.  A part can handle this as shown in MyPartCreateLink above.
  507.  
  508. Parts can now use the Clear method to replace promises in a link without notifying existing destinations.  Clear may be called with the existing update ID; by not calling ContentUpdated, destinations won't be notified (as shown in MyPartCreateLink).  If ContentUpdated is called with the existing update ID, the circular link update alert will appear.
  509.  
  510. Continued in Part 3...
  511.